package com.swak.persistence.mybatis.filter;

import java.lang.reflect.Method;

import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.MappedStatement.Builder;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.session.RowBounds;

import com.swak.persistence.datasource.DataSourceHolder;
import com.swak.persistence.dialect.Dialect;

/**
 * 分页插件
 * 
 * @author lifeng
 * @date 2021年2月1日 下午2:31:18
 */
public class PagingFilter extends AbstractFilter {

	@Override
	protected boolean support(Method method) {
		return method.getName().equals("query");
	}

	@Override
	public Object doFilter(Invocation invocation, FilterChain chain) throws Throwable {
		final Object[] queryArgs = invocation.getArgs();
		final RowBounds rowBounds = (RowBounds) queryArgs[2];
		int offset = rowBounds.getOffset();
		int limit = rowBounds.getLimit();
		Dialect dialect = DataSourceHolder.getDialect();
		if ((offset != RowBounds.NO_ROW_OFFSET || limit != RowBounds.NO_ROW_LIMIT) && dialect.supportsLimit()) {
			MappedStatement ms = (MappedStatement) queryArgs[0];
			Object parameter = queryArgs[1];
			BoundSql boundSql = ms.getBoundSql(parameter);
			String sql = boundSql.getSql().replaceAll("\\s{2,}", " ").trim();
			sql = dialect.getLimitString(sql, offset, limit);
			offset = RowBounds.NO_ROW_OFFSET;
			limit = RowBounds.NO_ROW_LIMIT;
			queryArgs[2] = new RowBounds(offset, limit);
			BoundSql newBoundSql = copyFromBoundSql(ms, boundSql, sql);
			MappedStatement newMs = copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
			queryArgs[0] = newMs;
		}
		return chain.filter(invocation);
	}

	/**
	 * 复制 MappedStatement
	 * 
	 * @param ms
	 * @param newSqlSource
	 * @return
	 */
	protected MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
		Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource,
				ms.getSqlCommandType());
		builder.resource(ms.getResource());
		builder.fetchSize(ms.getFetchSize());
		builder.statementType(ms.getStatementType());
		builder.keyGenerator(ms.getKeyGenerator());
		String[] s = ms.getKeyProperties();
		if (s == null) {
			builder.keyProperty(null);
		} else {
			builder.keyProperty(s[0]);
		}
		builder.timeout(ms.getTimeout());
		builder.parameterMap(ms.getParameterMap());
		builder.resultMaps(ms.getResultMaps());
		builder.cache(ms.getCache());
		return builder.build();
	}

	/**
	 * 复制 BoundSql
	 * 
	 * @param ms
	 * @param newSqlSource
	 * @return
	 */
	protected BoundSql copyFromBoundSql(MappedStatement ms, BoundSql boundSql, String sql) {
		BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(),
				boundSql.getParameterObject());
		for (ParameterMapping mapping : boundSql.getParameterMappings()) {
			String prop = mapping.getProperty();
			if (boundSql.hasAdditionalParameter(prop)) {
				newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
			}
		}
		return newBoundSql;
	}

	public static class BoundSqlSqlSource implements SqlSource {
		BoundSql boundSql;

		public BoundSqlSqlSource(BoundSql boundSql) {
			this.boundSql = boundSql;
		}

		public BoundSql getBoundSql(Object parameterObject) {
			return boundSql;
		}
	}
}